﻿using System;
using System.Threading;
using System.Net;
using System.Net.Sockets;

namespace AsynchronousMethods.ExtensibleNonblocking
{
  public class Server
  {
    public void Serve (IPAddress address, int port)
    {
      ThreadPool.SetMinThreads (50, 50);
      TcpListener listener = new TcpListener (address, port);
      listener.Start ();
      while (true)
      {
        TcpClient c = listener.AcceptTcpClient ();
        ThreadPool.QueueUserWorkItem (DoReverseEcho, c);
      }
    }

    void DoReverseEcho (object client)
    {
      MessagingServices.BeginReverseEcho ((TcpClient)client, ReverseEchoCallback, client);
    }

    void ReverseEchoCallback (IAsyncResult r)
    {
      try
      {
        MessagingServices.EndReverseEcho (r);  // This will rethrow any exception
      }
      catch (Exception ex)
      {
        Console.WriteLine ("Error: " + ex.Message);
      }
      finally
      {
        ((TcpClient)r.AsyncState).Close ();
      }
    }
  }

  public class MessagingServices
  {
    public static IAsyncResult BeginReverseEcho (TcpClient client,
                                                 AsyncCallback callback,
                                                 object userState)
    {
      var re = new ReverseEcho ();
      re.Begin (client, callback, userState);
      return re;
    }

    public static byte [] EndReverseEcho (IAsyncResult r)
    {
      return ((ReverseEcho)r).End ();
    }
  }

  class ReverseEcho : IAsyncResult
  {
    TcpClient _client;
    NetworkStream _stream;
    object _userState;
    AsyncCallback _callback;
    ManualResetEvent _waitHandle = new ManualResetEvent (false);
    int _bytesRead = 0;
    byte [] _data = new byte [Program.MessageLength];
    Exception _exception;

    internal ReverseEcho () { }

    // IAsyncResult members:

    public object AsyncState { get { return _userState; } }
    public WaitHandle AsyncWaitHandle { get { return _waitHandle; } }
    public bool CompletedSynchronously { get { return false; } }
    public bool IsCompleted
    {
      get { return _waitHandle.WaitOne (0, false); }
    }

    internal void Begin (TcpClient c, AsyncCallback callback, object state)
    {
      _client = c;
      _callback = callback;
      _userState = state;
      try
      {
        _stream = _client.GetStream ();
        Read ();
      }
      catch (Exception ex) { ProcessException (ex); }
    }

    internal byte [] End ()     // Wait for completion + re-throw any error.
    {
      AsyncWaitHandle.WaitOne ();
      if (_exception != null) throw _exception;
      return _data;
    }

    void Read ()   // This is always called from an exception-handled method
    {
     while (true)
      {
        IAsyncResult r = _stream.BeginRead
         (_data, _bytesRead, _data.Length - _bytesRead, ReadCallback, null);

        // This will nearly always return in the next line:
        if (!r.CompletedSynchronously) return;   // Handled by callback
        if (!EndRead (r)) break;
      }
     Write ();
    }

    void ReadCallback (IAsyncResult r)
    {
      try
      {
        if (r.CompletedSynchronously) return;
        if (EndRead (r))
        {
          Read ();       // More data to read!
          return;
        }
        Write ();
      }
      catch (Exception ex) { ProcessException (ex); }
    }

    bool EndRead (IAsyncResult r)   // Returns false if there’s no more data
    {
      int chunkSize = _stream.EndRead (r);
      _bytesRead += chunkSize;
      return chunkSize > 0 && _bytesRead < _data.Length;   // More to read
    }

    void Write ()
    {
      Array.Reverse (_data);
      _stream.BeginWrite (_data, 0, _data.Length, WriteCallback, null);
    }

    void WriteCallback (IAsyncResult r)
    {
      try { _stream.EndWrite (r); }
      catch (Exception ex) { ProcessException (ex); }
      Cleanup ();
    }

    void ProcessException (Exception ex)
    {
      _exception = ex;   // This will get re-thrown when the 
      Cleanup ();        // consumer calls the End method.
    }

    void Cleanup ()
    {
      try
      {
        if (_stream != null) _stream.Close ();
      }
      catch (Exception ex)
      {
        if (_exception != null) _exception = ex;
      }
      // Signal that we're done and fire the callback.
      _waitHandle.Set ();
      if (_callback != null) _callback (this);
    }
  }

}
